<?php
header("Content-type: text/html; charset=utf-8");
//初始化服务器时间为中国时间
date_default_timezone_set("PRC");

/**
 * Mlog日志记录类
 * @author 张云江
 * 
 */

class Mlog
{
    /**
     * 默认配置
     * 参数说明：
     * app项目名称
     * rootdir日志根目录
     * savedir日志保存目录
     * filename文件名称
     * ext日志文件后缀
     * pattern日志内容格式
     * level_df日志级别 
     * level日志级别列表，从做到右级别依次增大
     * enter日志换行符
     * filesize日志最大限制100MB（100X1024X1024），超过时会生成新文件
     * ismaxfile是否启用日志文件达到filesize时新建文件
     */
    private static $d_conf = array(
        "app" => "default/",
        "rootdir" => "./mlog/",
        "savedir" => "Ym/d/",
        "filename" => "Ymd",
        "ext" => "txt",
        "islog" => true,
        "pattern" => array("dt" => "Y-m-d H:i:s.u", "flie" => true, "function" => true, "line" => true, "method" => true, "split" => " ==== "),
        "level_df" => "TRACE",
        "level" => array("TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"),
        "filesize" => 2097152,
        "ismaxfile" => true
    );

    /**
     * 日志配置项(经过与自定义配置合并后)
     * @var array
     */
    private static $conf = array();

    /**
     * 日志级别
     * @var string
     */
    private static $method = null;

    /**
     * 日志打印来源
     * @var array
     */
    private static $backtrace = array();

    /**
     * 日志内容处理
     */
    private static $message = null;

    /**
     * 设置日志文件名称
     * @param string $path 日志目录路径
     * @return string
     */
    private static function setFileName($path, $method = "sys")
    {
        $conf = self::$conf;

        //如果没有启用文件大小限制
        if ($conf["ismaxfile"] == false)
            return $method . '_' . self::getDateTime($conf["filename"]) . ".{$conf['ext']}";

        //如果启用了文件大小限制
        $file = glob("{$path}{$method}_*.{$conf['ext']}");
        $filename = $method . '_' . self::getDateTime("Ymd_His") . ".{$conf['ext']}";
        if (count($file) > 0) {
            rsort($file);
            if (filesize($file[0]) < $conf['filesize']) $filename = pathinfo($file[0])["basename"];
        }
        return $filename;
    }

    /**
     * 设置日志内容
     */
    private static function setMessage($message)
    {
        $conf = self::$conf;
        if (!empty($message) && (is_array($message) || is_object($message)))
            $message = json_encode($message, JSON_UNESCAPED_UNICODE);

        $message = str_replace("\r\n", "", $message);
        $message = str_replace("\n", "", $message);
        $message = str_replace("\r", "", $message);
        $message = preg_replace("/[ |\t]{2,}/", " ", $message);
        self::$message = empty($message) ? "" : "{$conf["pattern"]["split"]}{$message}";
    }

    /**
     * 判断是否写入日志，分别判断日志级别是否满足或者日志类型是否定义等
     */
    private static function isLogLevel()
    {
        $conf = self::$conf;
        if (!in_array(strtoupper(self::$method), $conf["level"]) || array_keys($conf["level"], strtoupper(self::$method), true)[0] < array_keys($conf["level"], strtoupper($conf["level_df"]), true)[0]) {
            return false;
        }
        return true;
    }

    /**
     * 设置日志内容头
     * @return string
     */
    private static function setLogHead()
    {
        $conf = self::$conf;
        $backtrace = self::$backtrace;
        $method = self::$method;
        $head = self::getDateTime($conf["pattern"]["dt"]) . " "; //时间
        $head .= $conf["pattern"]["method"] === true ? "[{$method}]" : "";
        $head .= $conf["pattern"]["flie"] === true ? "[file::{$backtrace['file']}]" : ""; //日志文件
        $head .= $conf["pattern"]["function"] === true ? "[function::{$backtrace['function']}]" : ""; //日志源码函数
        $head .= $conf["pattern"]["line"] === true ? "[line::{$backtrace['line']}]" : ""; //日志源码行数
        return $head;
    }

    /**
     * 处理函数调用来源
     * @param array $backtrace
     */
    private static function setbackTrace($backtrace)
    {
        $back = array();
        /* foreach ($backtrace as $val)
        {
            $back["file"] = $back["file"]."-".$val["file"];
            $back["function"] = $back["function"]."-".$val["function"];
            $back["line"] = $back["line"]."-".$val["line"];
        } */

        //文件全路径
        $file_path = $backtrace[1]["file"];
        //文件路径最后两个节点
        //$farr = explode("/", str_replace('\\', '/', $file_path));
        //$fdir = count($farr);
        //$file_path = "{$farr[$fdir-2]}/{$farr[$fdir-1]}";
        $back["file"] = $file_path;
        $back["function"] = $backtrace[2]["function"];
        $back["line"] = $backtrace[1]["line"];
        return $back;
    }

    /**
     * 获取格式的时间
     * @param string $format 时间格式
     * @param string $utimestamp 时间戳
     */
    private static function getDateTime($format = 'Y-m-d H:i:s.u.s')
    {
        if (stripos($format, "u") === false) { //普通时间
            return date($format, time());
        } else { //带毫秒的时间
            $utimestamp = microtime();
            $utarr = explode(" ", $utimestamp);
            $datetime = date("Y-m-d H:i:s", $utarr[1]);
            $mlarr = explode(".", $utarr[0] * 1000);
            return "{$datetime}." . str_pad($mlarr[0], 3, "0", STR_PAD_LEFT) . "." . str_pad($mlarr[1], 3, "0", STR_PAD_RIGHT);
        }
    }

    /**
     * 写入日志
     */
    private static function logWrite($method = "sys")
    {
        $conf = self::$conf;
        //判断日志级别是否写入
        $message = self::$message;
        //内容为空时主要为了某些时候创建日志文件
        if ((!self::isLogLevel() || $conf["islog"] === false) && !empty($message))
            return false;

        //组装目录路径并创建目录
        $path = "{$conf['rootdir']}{$conf['app']}/" . self::getDateTime($conf["savedir"]);
        //创建目录
        !is_dir($path) && @mkdir($path, 0777, true);
        //日志文件名
        $filename = self::setFileName($path, $method);
        $path = $path . $filename;
        //检查文件是否可写并设置文件的访问和修改时间
        if (!is_writable($path))
            @touch($path);

        //组装日志内容
        $head = self::setLogHead();
        $data = self::$message;
        $write_data = $head . $data . PHP_EOL;
        if (file_exists($path) && empty($data))
            return true;
        //打开文件并写入数据
        $handle = @fopen($path, 'a');
        if ($handle) {
            fwrite($handle, $write_data);
            fclose($handle);
        }
    }

    /**
     * 初始化与参数设置
     * @param mixed $method
     * @param mixed $args 参数顺序说明，第一个为日志内容
     */
    private static function logInit($method, $message)
    {
        //初始化配置及参数信息
        self::$backtrace = self::setbackTrace(debug_backtrace());
        self::$method = $method;
        self::setMessage($message);
        self::logWrite($method);
    }

    /**
     * 处理默认配置与传入配置信息
     * @param array $conf 自定义配置信息
     */
    public static function setConfig($conf = array(), $allConf = array())
    {
        $dconf = self::$d_conf;
        //此处主要为了满足多个项目共同调用时，不再单独配置日志根目录
        if (!empty($allConf)) {
            foreach ($allConf as $key => $val) {
                if (is_array($val)) {
                    foreach ($val as $kobj => $obj) {
                        $dconf[$key][$kobj] = $obj;
                    }
                } else {
                    $dconf[$key] = $val;
                }
            }
        }
        //针对有每个项目的独立配置定义
        if (!empty($conf)) {
            foreach ($conf as $key => $val) {
                if (is_array($val)) {
                    foreach ($val as $kobj => $obj) {
                        $dconf[$key][$kobj] = $obj;
                    }
                } else {
                    $dconf[$key] = $val;
                }
            }
        }
        self::$conf = $dconf;

        //返回给请求的地方，用于如系统级错误日志打印
        $path = $dconf["rootdir"] . $dconf["app"] . "/" . self::getDateTime($dconf["savedir"]);
        $file_name = self::setFileName($path);
        return $path . $file_name;
    }

    /**
     * trace日志打印
     * @param mixed $message
     */
    public static function trace($message)
    {
        self::logInit("trace", $message);
    }

    /**
     * debug日志打印
     * @param mixed $message
     */
    public static function debug($message)
    {
        self::logInit("debug", $message);
    }

    /**
     * info日志打印
     * @param mixed $message
     */
    public static function info($message)
    {
        self::logInit("info", $message);
    }

    /**
     * warn日志打印
     * @param mixed $message
     */
    public static function warn($message)
    {
        self::logInit("warn", $message);
    }

    /**
     * error日志打印
     * @param mixed $message
     */
    public static function error($message)
    {
        self::logInit("error", $message);
    }

    /**
     * fatal日志打印
     * @param mixed $message
     */
    public static function fatal($message)
    {
        self::logInit("fatal", $message);
    }
}
